package org.weasel.views;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.ViewPart;
import org.weasel.business.Developer;
import org.weasel.business.Item;
import org.weasel.business.Iteration;
import org.weasel.business.Project;
import org.weasel.business.Release;
import org.weasel.business.Settings;
import org.weasel.business.Unscheduled;
import org.weasel.business.UserStory;
import org.weasel.business.Utilities;
import org.weasel.wizards.CreateDeveloperWizard;
import org.weasel.wizards.UserStoryWizard;

/**
 * This sample class demonstrates how to plug-in a new workbench view. The view
 * shows data obtained from the model. The sample creates a dummy model on the
 * fly, but a real implementation would connect to the model available either in
 * this or another plug-in (e.g. the workspace). The view is connected to the
 * model using a content provider.
 * <p>
 * The view uses a label provider to define how model objects should be
 * presented in the view. Each view can present the same model objects using
 * different labels and icons, if needed. Alternatively, a single label provider
 * can be shared between views in order to ensure that objects of the same type
 * are presented in the same way everywhere.
 * <p>
 */

public class Navigator extends ViewPart {
	public static final String ID = "org.weasel.views.Navigator";

	private TreeViewer viewer;

	private DrillDownAdapter drillDownAdapter;

	private Action refreshAction;

	private Action expandChildsAction;

	private Action doubleClickAction;

	private Action collapseChildsAction;
	
	private Action userStoryWizardAction;
	
	private Action deleteProjectAction;
	
	private Action createDeveloperAction;
	
	private ArrayList<Developer> developers;
	private Settings settings;

	/*
	 * The content provider class is responsible for providing objects to the
	 * view. It can wrap existing objects in adapters or simply return objects
	 * as-is. These objects may be sensitive to the current input of the view,
	 * or ignore it and always show the same content (like Task List, for
	 * example).
	 */
	
	public ArrayList<Developer> getDevelopers() {
		return developers;
	}
	
	public Settings getSettings() {
		return settings;
	}

	/***************************************************************************
	 * This method reads the projects and their associated information from the
	 * xml files and then it creates the tree and sets the created tree as the
	 * actual tree of the navigator.
	 * 
	 * This method can be called from outside the Navigator class with the
	 * following code: Utilities.callCreateAndUpdateTree();
	 */

	public void createAndUpdateTree() {
		TreeParent invisibleRoot = null;
		TreeParent companyName = null;
		try {
			settings = new Settings("Settings.xml");
			companyName = new TreeParent(settings.getCompanyName(), null);
			developers = settings.getDevelopers();

			ArrayList<Project> projects = settings.getProjects();
			for (Project project : projects) {
				System.out.println(project.getName() + " developers:");
				for (Developer d : project.getDevelopers()) {
					System.out.println(d.getName());
				}
				System.out.println("");
				
				TreeParent projectElement = new TreeParent(project.getName(),project);
				companyName.addChild(projectElement);

				TreeParent releases = new TreeParent("Releases", null);
				projectElement.addChild(releases);

				for (Release release : project.getReleases()) {
					TreeParent rel = new TreeParent(release.getName(), release);
					releases.addChild(rel);
					for (Iteration iteration : release.getIterations()) {
						TreeParent iterationElement = new TreeParent(iteration.getName(), iteration);
						rel.addChild(iterationElement);
						for (UserStory userStory : iteration.getUserStories()) {
							TreeObject userStoryElement = new TreeObject(userStory.getName(), userStory);
							// userStoryElement.setID(userStory.getId());
							// userStoryElement.setType("userstory");
							iterationElement.addChild(userStoryElement);
						}
					}

					Unscheduled releaseUnscheduled = release.getUnscheduled();
					TreeParent unschEl = new TreeParent("Unscheduled", releaseUnscheduled);
					rel.addChild(unschEl);

					ArrayList<UserStory> unschUserStories = releaseUnscheduled.getUserStories();
					for (UserStory story : unschUserStories) {
						TreeObject storyEl = new TreeObject(story.getName(),story);
						// storyEl.setID(story.getId());
						// storyEl.setType("userstory");
						unschEl.addChild(storyEl);
					}
				}

				Unscheduled projectUnscheduled = project.getUnscheduled();
				TreeParent projUnschEl = new TreeParent("Unscheduled", projectUnscheduled);
				projectElement.addChild(projUnschEl);
				ArrayList<UserStory> projectUnscheduledStories = projectUnscheduled.getUserStories();
				for (UserStory story : projectUnscheduledStories) {
					TreeObject storyEl = new TreeObject(story.getName(), story);
					projUnschEl.addChild(storyEl);
				}

			}
			invisibleRoot = new TreeParent("", null);
			invisibleRoot.addChild(companyName);

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		viewer.setInput(invisibleRoot);

		viewer.setExpandedState(companyName, true);
	}

	/**
	 * This method expands all the childs of a treeParent passed as parameter
	 * 
	 * @param parent
	 */
	private void expandChilds(TreeObject node) {
		if (node instanceof TreeParent) {
			TreeParent parent = (TreeParent) node;
			viewer.setExpandedState(parent, true);
			if (parent.hasChildren()) {
				for (TreeObject child : parent.getChildren()) {
					expandChilds(child);
				}
			}
		}
	}

	/**
	 * This method expands all the childs of a treeParent passed as parameter
	 * 
	 * @param parent
	 */
	private void collapseChilds(TreeObject node) {
		if (node instanceof TreeParent) {
			TreeParent parent = (TreeParent) node;
			viewer.setExpandedState(parent, false);
			if (parent.hasChildren()) {
				for (TreeObject child : parent.getChildren()) {
					expandChilds(child);
				}
			}
		}
	}

	private void addDragAndDrop() {
		Transfer[] transferTypes = new Transfer[] { TextTransfer.getInstance() };
		int operations = DND.DROP_MOVE | DND.DROP_COPY;
		final TreeObject[] selectedUserStory = new TreeObject[1];
		final Project[] project = new Project[1];
		final Item[] sourceParent = new Item[1];
		final Item[] targetParent = new Item[1];
		final UserStory[] userStory = new UserStory[1];
		viewer.addDragSupport(operations, transferTypes, new DragSourceListener() {
					public void dragFinished(DragSourceEvent event) {
						// TODO Auto-generated method stub
					}

					public void dragSetData(DragSourceEvent event) {
						// TODO Auto-generated method stub
						event.data = selectedUserStory[0].getName();
					}

					public void dragStart(DragSourceEvent event) {
						// TODO Auto-generated method stub
						event.doit = false;
						ISelection selection = viewer.getSelection();
						Object obj = ((IStructuredSelection) selection).getFirstElement();
						if (!(obj instanceof TreeParent)) {
							selectedUserStory[0] = (TreeObject) obj;
							userStory[0] = (UserStory)selectedUserStory[0].getItem();
							sourceParent[0] = selectedUserStory[0].getParent().getItem();
							event.doit = true;
						}
					}
				});

		viewer.addDropSupport(operations, transferTypes, new DropTargetListener() {
					public void dragEnter(DropTargetEvent event) {
						// TODO Auto-generated method stub
					}

					public void dragLeave(DropTargetEvent event) {
						// TODO Auto-generated method stub

					}

					public void dragOperationChanged(DropTargetEvent event) {
						// TODO Auto-generated method stub

					}

					public void dragOver(DropTargetEvent event) {
						// TODO Auto-generated method stub
						event.detail = DND.DROP_NONE;
						TreeItem item = (TreeItem) event.item;
						if (item != null) {
							TreeObject object = (TreeObject) item.getData();
							if (item != null && (object.getItem() instanceof Iteration || object.getItem() instanceof Unscheduled)){
								targetParent[0] = object.getItem();
								while (!(object.getItem() instanceof Project)) {
									object = object.getParent();
								}
								project[0] = (Project) object.getItem();
								while (!(selectedUserStory[0].getItem() instanceof Project)) {
									selectedUserStory[0] = selectedUserStory[0].getParent();
								}
								if (object.getName().equals(selectedUserStory[0].getName())) {
									event.detail = DND.DROP_MOVE;
								}
							}
						}
					}

					public void drop(DropTargetEvent event) {
						// TODO Auto-generated method stub
						if (event.data == null) {
							event.detail = DND.DROP_NONE;
							return;
						}
						try {
							if (sourceParent[0] instanceof Iteration) {
								Iteration sourceIteration = (Iteration)sourceParent[0];
								sourceIteration.removeUserStory(userStory[0].getId());
							} else {
								Unscheduled sourceUnscheduled = (Unscheduled)sourceParent[0];
								sourceUnscheduled.removeUnscheduledUserStory(userStory[0].getId());
							}
							
							if (targetParent[0] instanceof Iteration) {
								Iteration targetIteration = (Iteration)targetParent[0];
								targetIteration.addUserStory(userStory[0]);
							} else {
								Unscheduled targetUnscheduled = (Unscheduled)targetParent[0];
								targetUnscheduled.addUnscheduledUserStory(userStory[0]);
							}
							Utilities.callCreateAndUpdateTree();
							//expandChilds(selectedUserStory[0]);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}

					public void dropAccept(DropTargetEvent event) {
						// TODO Auto-generated method stub
					}
				});
	}



	class TreeObject implements IAdaptable {
		private String name;

		private String id;

		private String type;

		private TreeParent parent;

		private Item item;

		public TreeObject(String name) {
			this.name = name;
		}

		public TreeObject(String name, Item item) {
			this.name = name;
			this.id = "";
			this.type = "";
			this.item = item;
		}

		public String getName() {
			return name;
		}

		public Item getItem() {
			return this.item;
		}

		public void setItem(Item item) {
			this.item = item;
		}

		public String getID() {
			return id;
		}

		public String getType() {
			return type;
		}

		public void setID(String id) {
			this.id = id;
		}

		public void setType(String type) {
			this.type = type;
		}

		public void setParent(TreeParent parent) {
			this.parent = parent;
		}

		public TreeParent getParent() {
			return parent;
		}

		@Override
		public String toString() {
			return getName();
		}

		public Object getAdapter(Class key) {
			return null;
		}
	}

	class TreeParent extends TreeObject {
		private ArrayList<TreeObject> children;

		public TreeParent(String name, Item item) {
			super(name, item);
			children = new ArrayList<TreeObject>();
		}

		public void addChild(TreeObject child) {
			children.add(child);
			child.setParent(this);
		}

		public void removeChild(TreeObject child) {
			children.remove(child);
			child.setParent(null);
		}

		public TreeObject[] getChildren() {
			return children.toArray(new TreeObject[children.size()]);
		}

		public boolean hasChildren() {
			return children.size() > 0;
		}
	}

	class ViewContentProvider implements IStructuredContentProvider,
			ITreeContentProvider {
		private TreeParent invisibleRoot;

		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
			initialize();
		}

		public void dispose() {
		}

		public Object[] getElements(Object parent) {
			if (parent.equals(getViewSite())) {
				if (invisibleRoot == null) {
					initialize();
				}
				return getChildren(invisibleRoot);
			}
			return getChildren(parent);
		}

		public Object getParent(Object child) {
			if (child instanceof TreeObject) {
				return ((TreeObject) child).getParent();
			}
			return null;
		}

		public Object[] getChildren(Object parent) {
			if (parent instanceof TreeParent) {
				return ((TreeParent) parent).getChildren();
			}
			return new Object[0];
		}

		public boolean hasChildren(Object parent) {
			if (parent instanceof TreeParent) {
				return ((TreeParent) parent).hasChildren();
			}
			return false;
		}

		/*
		 * This method is called whenever the input of the tree changes. The
		 * initial loading of the tree is done in the createPartControl method.
		 */
		private void initialize() {

		}

	}

	class ViewLabelProvider extends LabelProvider {

		@Override
		public String getText(Object obj) {
			return obj.toString();
		}

		@Override
		public Image getImage(Object obj) {
			String imageKey = ISharedImages.IMG_OBJ_FILE;
			if (obj instanceof TreeParent) {
				imageKey = ISharedImages.IMG_OBJ_FOLDER;
			}
			return PlatformUI.getWorkbench().getSharedImages().getImage(imageKey);
		}
	}

	class NameSorter extends ViewerSorter {
	}

	/**
	 * The constructor.
	 */
	public Navigator() {

	}

	/**
	 * This is a callback that will allow us to create the viewer and initialize
	 * it. This method creates and initializes the Navigator view.
	 */
	@Override
	public void createPartControl(Composite parent) {
		viewer = new TreeViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
		addDragAndDrop();
		drillDownAdapter = new DrillDownAdapter(viewer);
		viewer.setContentProvider(new ViewContentProvider());
		viewer.setLabelProvider(new ViewLabelProvider());
		viewer.setSorter(new NameSorter());
		// Here the tree is created and initialized after the loading of the
		// Navigator
		createAndUpdateTree();
		makeActions();
		hookContextMenu();
		hookDoubleClickAction();
		contributeToActionBars();
	}

	private void hookContextMenu() {
		MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				Navigator.this.fillContextMenu(manager);
				enableDisableButtons();
			}
		});
		Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}
	
	/**
	 * This method enables or disables some functionalities based on the selected item of
	 * the tree.
	 *
	 */
	private void enableDisableButtons() {
		TreeObject selected = getSelectedTreeObject();
		Item item = selected.item;
		if(item instanceof Project || item instanceof Release || item instanceof Iteration || item instanceof Unscheduled || selected.name.equals("Releases")) {
			userStoryWizardAction.setEnabled(true);
		} else {
			userStoryWizardAction.setEnabled(false);
		}
		if(item instanceof Project){
			deleteProjectAction.setEnabled(true);
		}else{
			deleteProjectAction.setEnabled(false);
		}
	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalPullDown(IMenuManager manager) {
		manager.add(refreshAction);
		manager.add(new Separator());
		manager.add(expandChildsAction);
		manager.add(collapseChildsAction);
	}

	private void fillContextMenu(IMenuManager manager) {
		manager.add(refreshAction);
		manager.add(expandChildsAction);
		manager.add(collapseChildsAction);
		manager.add(userStoryWizardAction);
		manager.add(deleteProjectAction);
		manager.add(new Separator());
		drillDownAdapter.addNavigationActions(manager);
		// Other plug-ins can contribute there actions here
		manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
	}

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.add(refreshAction);
		manager.add(expandChildsAction);
		manager.add(collapseChildsAction);
		manager.add(createDeveloperAction);
		manager.add(new Separator());
		drillDownAdapter.addNavigationActions(manager);
	}

	private void makeActions() {
		refreshAction = new Action() {
			@Override
			public void run() {
				createAndUpdateTree();
			}
		};
		refreshAction.setText("refresh");
		refreshAction.setToolTipText("Refresh the project tree");
		try {
			refreshAction.setImageDescriptor(ImageDescriptor.createFromURL(new URL("file:icons/refresh.gif")));
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		expandChildsAction = new Action() {
			@Override
			public void run() {
				TreeObject selectedNode = getSelectedTreeObject();
				expandChilds(selectedNode);
			}
		};
		expandChildsAction.setText("Expand all");
		expandChildsAction.setToolTipText("Expands all the children of a selected node and the node itself.");
		try {
			expandChildsAction.setImageDescriptor(ImageDescriptor.createFromURL(new URL("file:icons/expandall.gif")));
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		collapseChildsAction = new Action() {
			@Override
			public void run() {
				TreeObject selectedNode = getSelectedTreeObject();
				collapseChilds(selectedNode);
			}
		};
		collapseChildsAction.setText("Collapse all");
		collapseChildsAction.setToolTipText("Collapses all the children of a selected node and the node itself.");
		try {
			collapseChildsAction.setImageDescriptor(ImageDescriptor.createFromURL(new URL("file:icons/collapseall.gif")));
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		doubleClickAction = new Action() {
			@Override
			public void run() {
				TreeObject obj = getSelectedTreeObject();
				if (obj.getItem() instanceof UserStory) {
					try {
						PlatformUI.getWorkbench().getActiveWorkbenchWindow()
								.getActivePage().showView(
										"org.weasel.views.UserStoryDetail");
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					UserStory userStory = (UserStory) obj.getItem();
					IWorkbenchPage page = PlatformUI.getWorkbench()
							.getActiveWorkbenchWindow().getActivePage();
					IViewPart viewPart = page.findView(UserStoryDetail.ID);
					UserStoryDetail detail = (UserStoryDetail) viewPart;
					detail.showUserStoryInformation(userStory);
					
				//if the selected item is a folder then the folder is expanded or collapsed
				} else if(obj instanceof TreeParent) {
					if(viewer.getExpandedState(obj)) {
						viewer.setExpandedState(obj, false);
					} else {
						viewer.setExpandedState(obj, true);
					}
				}
			}
		};
		
		userStoryWizardAction = new Action() {
			@Override
			public void run() {
				TreeObject selectedTreeObject = getSelectedTreeObject();
				Project project = getProjectOfItem(selectedTreeObject);
				Item item = selectedTreeObject.item;
				UserStoryWizard wizard = new UserStoryWizard(item, project);
		        WizardDialog dialog = new WizardDialog(viewer.getTree().getShell(), wizard);
		        dialog.setPageSize(new Point(450, 250));
		        dialog.open();
			}
		};

		userStoryWizardAction.setText("Add userstory");
		try {
			userStoryWizardAction.setImageDescriptor(ImageDescriptor.createFromURL(new URL("file:icons/add_userstory.gif")));
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		deleteProjectAction = new Action(){
			@Override
			public void run() {
				TreeObject t = getSelectedTreeObject();
				Project toDelete = (Project)t.getItem();
				Settings setts = null;
				
				try {
					setts = new Settings("Settings.xml");
					boolean yesAnswered = MessageDialog.openConfirm(viewer.getTree().getShell(), "Confirm", "Do you want to delete project \"" + toDelete.getName() + "\"?");
			        if(yesAnswered){			
			        	setts.removeProject(toDelete);
			        	Utilities.callCreateAndUpdateTree();
			        }
				} catch (Exception e) {
					e.printStackTrace();
				}
				
			}
		};
		deleteProjectAction.setText("delete project");
		try {
			deleteProjectAction.setImageDescriptor(ImageDescriptor.createFromURL(new URL("file:icons/delete.gif")));
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		createDeveloperAction = new Action() {
			@Override
			public void run() {
				CreateDeveloperWizard wizard;
				try {
					wizard = new CreateDeveloperWizard("Settings.xml");
					WizardDialog dialog = new WizardDialog(viewer.getTree().getShell(), wizard);
				    dialog.setPageSize(new Point(450, 250));
				    dialog.open();
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		};
		createDeveloperAction.setText("Create a new Developer");
		createDeveloperAction.setToolTipText("Click to create a new developer.");
		try {
			createDeveloperAction.setImageDescriptor(ImageDescriptor.createFromURL(new URL("file:icons/add_developer.gif")));
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	
	/**
	 * This method goes recursively to the treeobject which represents the project
	 * and returns the project object of that treeobject.
	 * @param object
	 * @return
	 */
	private Project getProjectOfItem(TreeObject object) {
		while (!(object.getItem() instanceof Project)) {
			object = object.getParent();
		}
		return (Project) object.getItem();
	}
	
	private TreeObject getSelectedTreeObject() {
		try{
			ISelection selection = viewer.getSelection();
			TreeObject selectedTreeObject = (TreeObject) ((IStructuredSelection) selection).getFirstElement();
			return selectedTreeObject;	
		}catch (Exception e){
			System.out.println("Exception caught: Navigator.getSelectedTreeObject()");
			return null;
		}
		
	}

	private void hookDoubleClickAction() {
		viewer.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				doubleClickAction.run();
			}
		});
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	@Override
	public void setFocus() {
		viewer.getControl().setFocus();
	}
}